/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.client.veditor.controller;

import java.awt.CardLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ResourceBundle;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.jcr.PropertyType;
import javax.swing.JEditorPane;

import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.Element;
import javax.swing.text.html.HTML;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import offset.nodes.Constants;

import offset.nodes.UrlQueryConstants;
import offset.nodes.client.model.HttpUtils;
import offset.nodes.client.model.NodeTypes;
import offset.nodes.client.model.ServerModel;
import offset.nodes.client.editor.Editor;
import offset.nodes.client.veditor.VirtualEditor;
import offset.nodes.client.veditor.VirtualPageEditorApplet;
import offset.nodes.client.veditor.model.messages.TemplateEditorResult;
import offset.nodes.client.veditor.controller.DataActions.AbstractDataAction;
import offset.nodes.client.editor.controller.EditorActions.AbstractEditorAction;
import offset.nodes.client.editor.controller.FileActions;
import offset.nodes.client.editor.controller.TableActions.DeleteRowAction;
import offset.nodes.client.editor.model.ComponentModel;
import offset.nodes.client.veditor.model.DataModel;
import offset.nodes.client.editor.model.DocumentContext;
import offset.nodes.client.editor.model.SchemaTree;
import offset.nodes.client.editor.model.StructureTree;
import offset.nodes.client.editor.model.messages.GetTemplatesForNodeTypePath;
import offset.nodes.client.editor.model.messages.TemplateInstanceEditorResult;
import offset.nodes.client.editor.model.messages.UploadData;
import offset.nodes.client.editor.model.messages.UploadPage;
import offset.nodes.client.veditor.model.messages.UploadVirtualPage;
import offset.nodes.client.editor.view.AuthentifyUserDialog;
import offset.nodes.client.editor.view.ConcurrentModificationDialog;
import offset.nodes.client.editor.view.EditorCaret;
import offset.nodes.client.editor.view.PopupTree;
import offset.nodes.client.editor.view.PopupTree.PopupNode;
import offset.nodes.client.veditor.view.SelectSchemaDialog;
import offset.nodes.client.editor.view.ServerErrorDialog;
import offset.nodes.client.editor.view.StyledDocumentWriter;
import offset.nodes.client.editor.view.StyledXMLWriter;
import offset.nodes.client.editor.view.UserCaretListener;
import offset.nodes.client.veditor.view.ExistingTemplates;
import offset.nodes.client.veditor.view.SelectQueryNameChooserPanel;
import offset.nodes.client.veditor.view.ev.EnumeratedValueChooser;
import offset.nodes.client.model.Server;
import offset.nodes.client.veditor.view.SelectElementActionNameChooserPanel;
import offset.nodes.client.view.ResultState;
import offset.nodes.client.view.SimpleDialog;
import offset.nodes.client.virtual.model.SchemaProperty;

class TreeUtils {

    protected DefaultMutableTreeNode findRoot(DefaultMutableTreeNode node) {
        while (node != null && !((Element) node.getUserObject()).getName().equals(HTML.Tag.DIV.toString())) {
            node = (DefaultMutableTreeNode) node.getParent();
        }
        return node;
    }
}

class TreePathMapper extends TreeUtils {

    DefaultMutableTreeNode root = null;

    public TreePathMapper(DefaultMutableTreeNode node) {
        this.root = findRoot(node);
    }

    public Object getPath(DefaultMutableTreeNode node, DefaultMutableTreeNode ignore, boolean forward) {
        LinkedList path = new LinkedList();
        while (node != null && node != root) {
            int index = ((DefaultMutableTreeNode) node.getParent()).getIndex(node);
            if (ignore != null && ignore.getParent() == node.getParent())
                if (forward && ignore.getParent().getIndex(ignore) < index)
                    index--;
                else if (!forward && ignore.getParent().getIndex(ignore) > index)
                    index++;

            if (!forward)
                index = node.getParent().getChildCount() - index - 1;

            path.addFirst(new Integer(index));

            node = (DefaultMutableTreeNode) node.getParent();
        }

        return path;
    }

    public DefaultMutableTreeNode getNode(Object p, boolean forward) {
        Collection path = (Collection) p;

        DefaultMutableTreeNode node = root;
        Iterator i = path.iterator();
        while (i.hasNext()) {
            Integer index = (Integer) i.next();
            if (forward)
                node = (DefaultMutableTreeNode) node.getChildAt(index.intValue());
            else
                node = (DefaultMutableTreeNode) node.getChildAt(node.getChildCount() - index.intValue() - 1);
        }

        return node;
    }
}

class TreeIterator extends TreeUtils {

    DataModel model = null;
    boolean forward = true;
    DefaultMutableTreeNode root = null;
    boolean lastMode = false;

    public TreeIterator(DataModel model, boolean forward) {
        this.model = model;
        this.forward = forward;
    }

    protected String getName(DataModel model, Element e) {
        if (e instanceof StructureTree.XMLElement)
            return ((StructureTree.XMLElement) e).getPath();
        if (!model.isData(e))
            return "";
        return model.getData(e);
    }

    protected boolean isEmptyDataElement(Element element) throws Exception {
        if (element instanceof StructureTree.XMLElement)
            return false;

        if (element.getEndOffset() - element.getStartOffset() == 1)
            return true;

        if (model.isData(element))
            if (model.isLeaf(element) && !model.onlyDescriptionText(element))
                return false;

        for (int i = 0; i < element.getElementCount(); i++) {
            Element child = element.getElement(i);
            if (model.isData(child))
                if (model.isLeaf(child) && !model.onlyDescriptionText(child))
                    return false;
                else if (!isEmptyDataElement(child))
                    return false;
        }

        return true;
    }

    protected boolean isEmptyDataNode(DefaultMutableTreeNode node) throws Exception {
        if (!forward && !lastMode)
            return isEmptyDataElement((Element) node.getUserObject());

        DefaultMutableTreeNode prev = node.getPreviousSibling();
        if (prev == null)
            return false;

        Element element = (Element) node.getUserObject();
        Element prevElement = (Element) prev.getUserObject();
        if (getName(model, element).equals(getName(model, prevElement)) && isEmptyDataElement(prevElement))
            return true;

        return false;
    }

    protected DefaultMutableTreeNode firstNode() throws Exception {
        return nextNode(root);
    }

    protected DefaultMutableTreeNode lastNode(DefaultMutableTreeNode node) throws Exception {
        lastMode = true;
        if (node.getChildCount() == 0) {
            if (isEmptyDataNode(node))
                node = lastNode(node.getPreviousSibling());

            return node;
        }

        return lastNode((DefaultMutableTreeNode) node.getChildAt(node.getChildCount() - 1));
    }

    protected DefaultMutableTreeNode nextNode(DefaultMutableTreeNode node) throws Exception {
        if (root == null)
            root = findRoot(node);

        if (lastMode)
            return lastNode(node);

        while (node != null) {
            if (forward)
                node = node.getNextNode();
            else
                node = node.getPreviousNode();

            if (node != null && node != root) {
                Element e = (Element) node.getUserObject();
                if ((e instanceof StructureTree.XMLElement && !isEmptyDataNode(node)) || (model.isData(e) && !isEmptyDataNode(node)))
                    return node;
            }
        }

        if (node == null && forward)
            return nextNode(root);
        else
            return lastNode(root);
    }
}

abstract class AbstractDataPopupItem implements PopupTree.PopupItem {

    VirtualEditor editor;

    public AbstractDataPopupItem(VirtualEditor editor) {
        this.editor = editor;
    }

    public boolean isTemplateMode() {
        return getVirtualEditor().getMode() == VirtualEditor.MODE_TEMPLATE_EDITOR
                || getVirtualEditor().getMode() == VirtualEditor.MODE_VIRTUAL_PAGE_EDITOR
                || getVirtualEditor().getMode() == VirtualEditor.MODE_VIRTUAL_PAGE_INSTANCE_EDITOR;
    }

    public VirtualEditor getVirtualEditor(){
        return editor;
    }
}


class TemplatePopup implements PopupTree.NodePopup {

    VirtualEditor editor;

    public TemplatePopup(VirtualEditor editor) {
        this.editor = editor;
    }

    public JPopupMenu getPopup() {
        JPopupMenu popup = new JPopupMenu();
        JMenuItem item = new JMenuItem();

        item.setAction(new DataActions.SaveTemplateAction(editor));
        popup.add(item);

        item = new JMenuItem();

        item.setAction(new DataActions.SaveTemplateAsAction(editor));
        popup.add(item);

        return popup;
    }

    public boolean isPopupNode(PopupNode node) {
        Element e = (Element) ((DefaultMutableTreeNode) node).getUserObject();
        DataModel model = new DataModel(new DocumentContext(getVirtualEditor().getBrowserPane()), getVirtualEditor().getProperties());
        if (model.isTemplate(e))
            return true;
        return false;
    }

    public VirtualEditor getVirtualEditor(){
        return editor;
    }
}

class InsertItem implements PopupTree.PopupItem {

    VirtualEditor editor;

    public InsertItem(VirtualEditor editor) {
        this.editor = editor;
    }

    public JMenuItem getItem() {
        JMenuItem item = new JMenuItem();

        item.setAction(new DataActions.InsertDataElementAction(editor));

        return item;
    }

    public boolean isItemNode(PopupNode node) {
        Element e = (Element) ((DefaultMutableTreeNode) node).getUserObject();
        if (e instanceof StructureTree.XMLElement)
            return true;
        return false;
    }
}


class RemoveItem implements PopupTree.PopupItem {

    VirtualEditor editor;

    public RemoveItem(VirtualEditor editor) {
        this.editor = editor;
    }

    public VirtualEditor getVirtualEditor(){
        return editor;
    }

    public JMenuItem getItem() {
        JMenuItem item = new JMenuItem();

        item.setAction(new DataActions.RemoveDataElementAction(editor));

        return item;
    }

    public boolean isItemNode(PopupNode node) {
        Element e = (Element) ((DefaultMutableTreeNode) node).getUserObject();
        DataModel model = new DataModel(new DocumentContext(getVirtualEditor().getBrowserPane()), getVirtualEditor().getProperties());
        if (!(e instanceof StructureTree.XMLElement) && model.isData(e))
            return true;
        return false;
    }
}


/**
 * Mark the selected element as readonly
 */
class UseTemplateItem implements PopupTree.PopupItem {

    VirtualEditor editor;

    public UseTemplateItem(VirtualEditor editor) {
        this.editor = editor;
    }

    public VirtualEditor getVirtualEditor(){
        return editor;
    }

    public JMenuItem getItem() {
        JMenuItem item = new JMenuItem();

        item.setAction(new DataActions.UseTemplateAction(editor));

        return item;
    }

    public boolean isItemNode(PopupNode node) {
        Element e = (Element) ((DefaultMutableTreeNode) node).getUserObject();
        DataModel model = new DataModel(new DocumentContext(getVirtualEditor().getBrowserPane()), getVirtualEditor().getProperties());
        if (model.isData(e)) {
            String data = model.getData(e);
            int last = data.lastIndexOf(Constants.PATH_SEPARATOR);
            if (last > 0)
                data = data.substring(last+1);
            if (!data.startsWith("@"))
                return true;
        }
        return false;
    }
}


/**
 * Map the selected element to a named query
 */
class MapQueryItem implements PopupTree.PopupItem {

    VirtualEditor editor;

    public MapQueryItem(VirtualEditor editor) {
        this.editor = editor;
    }

    public VirtualEditor getVirtualEditor(){
        return editor;
    }

    public JMenuItem getItem() {
        JMenuItem item = new JMenuItem();

        item.setAction(new DataActions.MapQueryAction(editor));

        return item;
    }

    public boolean isItemNode(PopupNode node) {
        Element e = (Element) ((DefaultMutableTreeNode) node).getUserObject();
        DataModel model = new DataModel(new DocumentContext(getVirtualEditor().getBrowserPane()), getVirtualEditor().getProperties());
        if (model.isData(e)) {
            String data = model.getData(e);
            int last = data.lastIndexOf(Constants.PATH_SEPARATOR);
            if (last > 0)
                data = data.substring(last+1);
            if (data.startsWith("@"))
                return true;
        }
        return false;
    }
}


/**
 * Map the selected element to a named query
 */
class RemoveQueryItem implements PopupTree.PopupItem {

    VirtualEditor editor;

    public RemoveQueryItem(VirtualEditor editor) {
        this.editor = editor;
    }

    public VirtualEditor getVirtualEditor(){
        return editor;
    }

    public JMenuItem getItem() {
        JMenuItem item = new JMenuItem();

        item.setAction(new DataActions.RemoveQueryAction(editor));

        return item;
    }

    public boolean isItemNode(PopupNode node) {
        Element e = (Element) ((DefaultMutableTreeNode) node).getUserObject();
        DataModel model = new DataModel(new DocumentContext(getVirtualEditor().getBrowserPane()), getVirtualEditor().getProperties());
        
        return model.getQueryName(e) != null;
    }
}


/**
 * Map the selected element to a named query
 */
class MapElementActionItem implements PopupTree.PopupItem {

    VirtualEditor editor;

    public MapElementActionItem(VirtualEditor editor) {
        this.editor = editor;
    }

    public VirtualEditor getVirtualEditor(){
        return editor;
    }

    public JMenuItem getItem() {
        JMenuItem item = new JMenuItem();

        item.setAction(new DataActions.MapElementActionAction(editor));

        return item;
    }

    public boolean isItemNode(PopupNode node) {
        return true;
    }
}


/**
 * Map the selected element to a named query
 */
class RemoveElementActionItem implements PopupTree.PopupItem {

    VirtualEditor editor;

    public RemoveElementActionItem(VirtualEditor editor) {
        this.editor = editor;
    }

    public VirtualEditor getVirtualEditor(){
        return editor;
    }

    public JMenuItem getItem() {
        JMenuItem item = new JMenuItem();

        item.setAction(new DataActions.RemoveElementActionAction(editor));

        return item;
    }

    public boolean isItemNode(PopupNode node) {
        Element e = (Element) ((DefaultMutableTreeNode) node).getUserObject();
        DataModel model = new DataModel(new DocumentContext(getVirtualEditor().getBrowserPane()), getVirtualEditor().getProperties());
        
        return model.getBrowserElementActionModel().getElementActionName(e) != null;
    }
}

/**
 * Mark the selected element as readonly
 */
class MarkReadOnlyItem implements PopupTree.PopupItem {

    VirtualEditor editor;

    public MarkReadOnlyItem(VirtualEditor editor) {
        this.editor = editor;
    }

    public VirtualEditor getVirtualEditor(){
        return editor;
    }

    public JMenuItem getItem() {
        JMenuItem item = new JMenuItem();

        item.setAction(new DataActions.MarkReadOnlyAction(editor));

        return item;
    }

    public boolean isItemNode(PopupNode node) {
        Element e = (Element) ((DefaultMutableTreeNode) node).getUserObject();
        DataModel model = new DataModel(new DocumentContext(getVirtualEditor().getBrowserPane()), getVirtualEditor().getProperties());
        if (model.isData(e) && !model.isReadOnly(e))
            return true;
        return false;
    }
}


/**
 * Mark the selected element as read-write.
 */
class MarkReadWriteItem implements PopupTree.PopupItem {

    VirtualEditor editor;

    public MarkReadWriteItem(VirtualEditor editor) {
        this.editor = editor;
    }

    public VirtualEditor getVirtualEditor(){
        return editor;
    }

    public JMenuItem getItem() {
        JMenuItem item = new JMenuItem();

        item.setAction(new DataActions.MarkReadWriteAction(editor));

        return item;
    }

    public boolean isItemNode(PopupNode node) {
        Element e = (Element) ((DefaultMutableTreeNode) node).getUserObject();
        DataModel model = new DataModel(new DocumentContext(getVirtualEditor().getBrowserPane()), getVirtualEditor().getProperties());
        if (model.isData(e) && model.isReadOnly(e))
            return true;
        return false;
    }
}


class NewTemplateItem implements PopupTree.PopupItem {

    VirtualEditor editor;

    public NewTemplateItem(VirtualEditor editor) {
        this.editor = editor;
    }

    public JMenuItem getItem() {
        JMenuItem item = new JMenuItem();

        item.setAction(new DataActions.NewTemplateAction(editor));

        return item;
    }

    public VirtualEditor getVirtualEditor(){
        return editor;
    }

    public boolean isItemNode(PopupNode node) {
        Element e = (Element) ((DefaultMutableTreeNode) node).getUserObject();
        DataModel model = new DataModel(new DocumentContext(getVirtualEditor().getBrowserPane()), getVirtualEditor().getProperties());
        if (DataActions.AbstractDataAction.schemaRoot != null && !model.isInTemplate(e))
            return true;
        return false;
    }
}

class MapElementToNewSchemaItem extends AbstractDataPopupItem {

    String elementName = null;

    public MapElementToNewSchemaItem(VirtualEditor editor) {
        super(editor);
    }

    public JMenuItem getItem() {
        JMenuItem item = new JMenuItem();

        item.setAction(new DataActions.MapElementToNewTypeAction(editor));
        item.setText(java.text.MessageFormat.format(java.util.ResourceBundle.getBundle("offset/nodes/client/editor/view/resources/EditorBundle").getString("mapToNewSchema"), new Object[]{elementName}));

        return item;
    }

    public boolean isItemNode(PopupNode node) {
        Element e = (Element) ((DefaultMutableTreeNode) node).getUserObject();
        DataModel model = new DataModel(new DocumentContext(getVirtualEditor().getBrowserPane()), getVirtualEditor().getProperties());
        if (isTemplateMode())
            return true;
        return false;
    }
}

class MapElementToExtendedSchemaItem extends AbstractDataPopupItem {

    String elementName = null;

    public MapElementToExtendedSchemaItem(VirtualEditor editor) {
        super(editor);
    }

    public JMenuItem getItem() {
        JMenuItem item = new JMenuItem();

        item.setAction(new DataActions.MapElementToExtendedTypeAction(editor));
        item.setText(java.text.MessageFormat.format(java.util.ResourceBundle.getBundle("offset/nodes/client/editor/view/resources/EditorBundle").getString("mapToExtendedSchema"), new Object[]{elementName}));

        return item;
    }

    public boolean isItemNode(PopupNode node) {
        Element e = (Element) ((DefaultMutableTreeNode) node).getUserObject();
        DataModel model = new DataModel(new DocumentContext(getVirtualEditor().getBrowserPane()), getVirtualEditor().getProperties());
        if (isTemplateMode())
            return true;
        return false;
    }
}

class MapElementToPageItem extends AbstractDataPopupItem {

    String elementName = null;

    public MapElementToPageItem(VirtualEditor editor) {
        super(editor);
    }

    public JMenuItem getItem() {
        JMenuItem item = new JMenuItem();

        item.setAction(new DataActions.MapElementToPageAction(editor));
        item.setText(java.text.MessageFormat.format(java.util.ResourceBundle.getBundle("offset/nodes/client/editor/view/resources/EditorBundle").getString("mapToPage"), new Object[]{elementName}));

        return item;
    }

    public boolean isItemNode(PopupNode node) {
        Element e = (Element) ((DefaultMutableTreeNode) node).getUserObject();
        if (!e.getAttributes().isDefined(DataModel.ATTRIBUTE_NAME_DATA))
            return false;

        DataModel model = new DataModel(new DocumentContext(getVirtualEditor().getBrowserPane()), getVirtualEditor().getProperties());
        String data = model.getData(e);
        if (!model.isProperty(e))
            return false;

        NodeTypes types = (NodeTypes) getVirtualEditor().getProperties().get(Editor.PROP_NODE_TYPES);
        // TODO: check for reference type

        return true;
    }
}

class RemovePageMappingItem extends AbstractDataPopupItem {

    String elementName = null;

    public RemovePageMappingItem(VirtualEditor editor) {
        super(editor);
    }

    public JMenuItem getItem() {
        JMenuItem item = new JMenuItem();

        item.setAction(new DataActions.RemovePageMappingAction(editor));
        item.setText(java.text.MessageFormat.format(java.util.ResourceBundle.getBundle("offset/nodes/client/editor/view/resources/EditorBundle").getString("removePageMapping"), new Object[]{elementName}));

        return item;
    }

    public boolean isItemNode(PopupNode node) {
        Element e = (Element) ((DefaultMutableTreeNode) node).getUserObject();
        if (!e.getAttributes().isDefined(DataModel.ATTRIBUTE_NAME_DATA) 
                || !e.getAttributes().isDefined(DataModel.ATTRIBUTE_NAME_PAGE))
            return false;

        return true;
    }
}

class MapElementToComponentItem extends AbstractDataPopupItem {

    String elementName = null;

    public MapElementToComponentItem(VirtualEditor editor) {
        super(editor);
    }

    public JMenuItem getItem() {
        JMenuItem item = new JMenuItem();

        item.setAction(new DataActions.MapElementToComponentAction(editor));
        item.setText(java.text.MessageFormat.format(java.util.ResourceBundle.getBundle("offset/nodes/client/veditor/view/resources/EditorBundle").getString("mapToComponent"), new Object[]{elementName}));

        return item;
    }

    public boolean isItemNode(PopupNode node) {
        Element e = (Element) ((DefaultMutableTreeNode) node).getUserObject();
        if (!e.getAttributes().isDefined(DataModel.ATTRIBUTE_NAME_DATA))
            return false;
        
        if (!e.getName().equals(HTML.Tag.P.toString())) // map only paragraph elements
            return false;
        
        DataModel model = new DataModel(new DocumentContext(getVirtualEditor().getBrowserPane()), getVirtualEditor().getProperties());
        String data = model.getData(e);
        if (model.isProperty(e))
            return false;
        

        NodeTypes types = (NodeTypes) getVirtualEditor().getProperties().get(Editor.PROP_NODE_TYPES);
        // TODO: check for mixin nodes:editor

        return true;
    }
}

class RemoveComponentMappingItem extends AbstractDataPopupItem {

    String elementName = null;

    public RemoveComponentMappingItem(VirtualEditor editor) {
        super(editor);
    }

    public JMenuItem getItem() {
        JMenuItem item = new JMenuItem();

        item.setAction(new DataActions.RemoveComponentMappingAction(editor));
        item.setText(java.text.MessageFormat.format(java.util.ResourceBundle.getBundle("offset/nodes/client/veditor/view/resources/EditorBundle").getString("removeComponentMapping"), new Object[]{elementName}));

        return item;
    }

    public boolean isItemNode(PopupNode node) {
        Element e = (Element) ((DefaultMutableTreeNode) node).getUserObject();
        if (!e.getAttributes().isDefined(ComponentModel.ATT_COMPONENT_ID) )
            return false;

        return true;
    }
}

public class DataActions {

    SetCaretAction setCaretAction = null;

    public DataActions() {
    }

    public SetCaretAction getCaretSetAction() {
        return setCaretAction;
    }

    public void setCaretSetAction(SetCaretAction caretSetAction) {
        this.setCaretAction = caretSetAction;
    }

    public static PopupTree.NodePopup[] getDataPopups(VirtualEditor editor) {
        return new PopupTree.NodePopup[]{
                    new PopupTree.ItemGroupsPopup(new PopupTree.ItemGroup[]{
                        new PopupTree.ItemGroup(new PopupTree.PopupItem[]{new InsertItem(editor)}),
                        new PopupTree.ItemGroup(new PopupTree.PopupItem[]{new NewTemplateItem(editor)}),
                        new PopupTree.ItemGroup(new PopupTree.PopupItem[]{new MarkReadOnlyItem(editor), new MarkReadWriteItem(editor)}),
                        new PopupTree.ItemGroup(new PopupTree.PopupItem[]{new MapElementToNewSchemaItem(editor), new MapElementToExtendedSchemaItem(editor), new RemoveItem(editor)}),
                        new PopupTree.ItemGroup(new PopupTree.PopupItem[]{new MapElementToPageItem(editor), new RemovePageMappingItem(editor)}),
                        new PopupTree.ItemGroup(new PopupTree.PopupItem[]{new MapElementToComponentItem(editor), new RemoveComponentMappingItem(editor)}),
                        new PopupTree.ItemGroup(new PopupTree.PopupItem[]{new MapQueryItem(editor), new RemoveQueryItem(editor), new UseTemplateItem(editor)}),
                        new PopupTree.ItemGroup(new PopupTree.PopupItem[]{new MapElementActionItem(editor), new RemoveElementActionItem(editor)})
                    })};
    }

    public static abstract class AbstractDataAction extends AbstractEditorAction {

        static JMenuItem newTemplate = null;
        static JMenuItem saveTemplate = null;
        static JMenuItem saveTemplateAs = null;
        static JMenuItem mapElement = null;
        static JMenuItem mapData = null;
        static File schema = null;
        static String schemaRoot = null;
        static File template;
        static boolean hasSchema = false;
        UpdateActions updateActions;
        VirtualEditor virtualEditor;
       static protected ResourceBundle bundle = ResourceBundle.getBundle("offset/nodes/client/veditor/view/resources/EditorBundle");

        public AbstractDataAction(VirtualEditor editor, String name) {
            super(name, editor);
            this.virtualEditor = editor;
            this.updateActions = new UpdateActions(editor);
        }

        public static ResourceBundle getBundle() {
            return bundle;
        }
        
        public JEditorPane getEditorPane(ActionEvent ae) {
            return getVirtualEditor().getBrowserPane();
        }        

        public VirtualEditor getVirtualEditor() {
            return virtualEditor;
        }

        public DataModel getModel() {
            return new DataModel(new DocumentContext(getVirtualEditor().getBrowserPane()), getVirtualEditor().getProperties());
        }
        
        public ComponentModel getComponentModel() {
            return new ComponentModel(new DocumentContext(getEditor().getBrowserPane()), getEditor());
        }

        protected void selectNode(DefaultMutableTreeNode node) {
            Element element = (Element) node.getUserObject();
            selectElement(element);
        }

        public void selectElement(Element element) {
            try {
                scrollToDocumentPosition(getVirtualEditor().getBrowserPane(), element.getStartOffset());
                if (element instanceof StructureTree.XMLElement)
                    getVirtualEditor().getBrowserPane().setCaretPosition(element.getEndOffset() - 1);
                else {
                    getVirtualEditor().getBrowserPane().setCaretPosition(element.getStartOffset());
                    getVirtualEditor().getBrowserPane().setSelectionEnd(element.getEndOffset() - 1);
                    getVirtualEditor().getBrowserPane().setSelectionStart(element.getStartOffset());
                }
                getVirtualEditor().getBrowserPane().grabFocus();
            } catch (Exception e) {
                e.printStackTrace();
            }

            getVirtualEditor().getStructure().selectElement(element);
            updateActions.update();
        }

        public void setPosition(int pos) {
            try {
                scrollToDocumentPosition(getVirtualEditor().getBrowserPane(), pos);
                getVirtualEditor().getBrowserPane().setCaretPosition(pos);
                getVirtualEditor().getBrowserPane().grabFocus();
            } catch (Exception e) {
                e.printStackTrace();
            }

            Element element = getModel().getRealParagraph(pos);
            getVirtualEditor().getStructure().selectElement(element);
            updateActions.update();
        }

        public DataNavigationFilter getNavigationFilter() {
            return (DataNavigationFilter) getVirtualEditor().getBrowserPane().getNavigationFilter();
        }
    }

    public static class InsertDataElementAction extends AbstractDataAction {

        public InsertDataElementAction(VirtualEditor editor) {
            super(editor, localizer.getString("InsertDataElement"));
        }

        public void action(ActionEvent e) throws Exception {
            if (getVirtualEditor().getStructureTree().getSelectionCount() == 0)
                return;

            Object o = ((DefaultMutableTreeNode) getVirtualEditor().getStructureTree().getSelectionPath().getLastPathComponent()).getUserObject();
            if (o == null || !(o instanceof StructureTree.XMLElement))
                return;
            StructureTree.XMLElement element = (StructureTree.XMLElement) o;
            DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) ((DefaultMutableTreeNode) getVirtualEditor().getStructureTree().getSelectionPath().getLastPathComponent()).getParent();
            getModel().insertDataElement(element.getPath(), (Element) parentNode.getUserObject());
            int pos = getVirtualEditor().getBrowserPane().getCaretPosition();
            if (pos > 0)
                getVirtualEditor().getBrowserPane().setCaretPosition(pos - 1);

            getVirtualEditor().getStructure().refresh();
        }
    }

    public static class RemoveDataElementAction extends AbstractDataAction {

        public RemoveDataElementAction(VirtualEditor editor) {
            super(editor, localizer.getString("RemoveDataElement"));
        }

        public void action(ActionEvent e) throws Exception {
            Element element = (Element) ((DefaultMutableTreeNode) getVirtualEditor().getStructureTree().getSelectionPath().getLastPathComponent()).getUserObject();
            getModel().removeDataElement(element);
            getVirtualEditor().getStructure().refresh();
        }
    }

    public static class DeleteTemplateAction extends AbstractDataAction {

        public DeleteTemplateAction(VirtualEditor editor) {
            super(editor, localizer.getString("DeleteTemplate"));
        }

        public void action(ActionEvent e) throws Exception {
            PopupTree.PopupNode node = (PopupTree.PopupNode) getVirtualEditor().getStructureTree().getSelectionPath().getLastPathComponent();
            Element element = (Element) node.getUserObject();
            if (!getModel().isTemplate(element))
                return;

            getModel().removeDataBlock(element);
            ((DefaultTreeModel) getVirtualEditor().getStructureTree().getModel()).reload(node);
        }
    }

    public static class SaveTemplateAction extends AbstractDataAction {

        public SaveTemplateAction(VirtualEditor editor) {
            super(editor, localizer.getString("SaveTemplate"));
        }

        public void action(ActionEvent e) throws Exception {
        }
    }

    public static class SaveDataAction extends AbstractDataAction {

        public SaveDataAction(VirtualEditor editor) {
            super(editor, java.util.ResourceBundle.getBundle("offset/nodes/client/editor/view/resources/EditorBundle").getString("File.saveServer"));
            setIcon(new javax.swing.ImageIcon(getClass().getResource("/offset/nodes/client/editor/view/resources/sc_save2.png")));
        }

        protected UploadData.Request getUploadRequest() throws Exception {
            Element template = getModel().findTemplate((Element) ((DefaultMutableTreeNode) getVirtualEditor().getStructureTree().getModel().getRoot()).getUserObject());

            String data = getModel().exportData(template);
            String type = getModel().getSchemaName(template);
            String uuid = getModel().getId(template);
            String dataPath = null;
            if (getVirtualEditor().getProperties().get(Editor.PROP_PARENT_PATH) != null
                    && ((String) getVirtualEditor().getProperties().get(Editor.PROP_PARENT_PATH)).length() > 0)
                dataPath = (String) getVirtualEditor().getProperties().get(Editor.PROP_PARENT_PATH);
            String instancePath = (String) getVirtualEditor().getProperties().get(Editor.PROP_INSTANCE_PATH);


            UploadData.Request request = new UploadData.Request();


            request.setData(data);
            request.setPath(dataPath);
            request.setInstancePath(instancePath);
            request.setUuid(uuid);

           // TODO: remove this. save the state of component editors first, as the name of the node may have changed.
           getModel().saveComponents(template, request);
           
            // save the state of the components
            ComponentModel componentModel = new ComponentModel(new DocumentContext(getEditor().getBrowserPane()), getEditor());
            componentModel.saveComponents(request);
           
            return request;
        }

        public void action(ActionEvent e) throws Exception {
            UploadData.Request request = getUploadRequest();
            if (request == null)
                return;

            try {
                String upload = (String) getVirtualEditor().getProperties().get(Editor.PROP_UPLOAD_TO);
                if (upload == null)
                    return;

                ServerModel model = new ServerModel(new URL(upload));
                UploadData.Response response = (UploadData.Response) model.sendRequest(request);

                if (response.getResult() == UploadData.Response.RESULT_CONCURRENT_MODIFICATION) {
                    ConcurrentModificationDialog dialog = new ConcurrentModificationDialog(null, true);
                    if (dialog.getReturnStatus() == ConcurrentModificationDialog.RET_OK) {
                        request.setLastModified(Calendar.getInstance());
                        response = (UploadData.Response) model.sendRequest(request);
                        if (response.getResult() != UploadData.Response.RESULT_SUCCESS)
                            new ServerErrorDialog(null, true);
                    }
                } else if (response.getResult() == UploadPage.Response.RESULT_SUCCESS) {

                    URL url = getVirtualEditor().getBrowserPane().getPage();
                    if (request.getPath() != null) {
                        String documentPath = (String) getVirtualEditor().getProperties().get(Editor.PROP_DOCUMENT_PATH);
                        String name = documentPath.substring(documentPath.lastIndexOf(Constants.PATH_SEPARATOR) + 1);
                        URL context = getContext();
                        url = new URL(context.getProtocol(), context.getHost(), context.getPort(), context.getPath() + request.getPath() + Constants.PATH_SEPARATOR + name);
                        System.out.println(url);
                    }

                    if (response.getForward() != null)
                        url = new URL(url, response.getForward());
                    else
                        url = new URL(HttpUtils.removeParameter(url.toString(), UrlQueryConstants.PARAM_MODE));

                    url = new URL(HttpUtils.addParameter(url.toString(),
                            Constants.PAR_RESULT,
                            Constants.PARVALUE_OK));
                    getVirtualEditor().getAppletContext().showDocument(url);
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    public static class SaveDataCommentAction extends SaveDataAction {

        public SaveDataCommentAction(VirtualEditor editor) {
            super(editor);
            String name = java.util.ResourceBundle.getBundle("offset/nodes/client/editor/view/resources/EditorBundle").getString("File.saveServerComment");
            putValue(NAME, name);
            putValue(SHORT_DESCRIPTION, name);
            setIcon(new javax.swing.ImageIcon(getClass().getResource("/offset/nodes/client/editor/view/resources/sc_saveComment.png")));
        }

        protected UploadData.Request getUploadRequest() throws Exception {
            Element template = getModel().findTemplate((Element) ((DefaultMutableTreeNode) getVirtualEditor().getStructureTree().getModel().getRoot()).getUserObject());

            String data = getModel().exportData(template);
            String type = getModel().getSchemaName(template);
            String uuid = getModel().getId(template);
            String dataPath = null;
            if (getVirtualEditor().getProperties().get(Editor.PROP_PARENT_PATH) != null
                    && ((String) getVirtualEditor().getProperties().get(Editor.PROP_PARENT_PATH)).length() > 0)
                dataPath = (String) getVirtualEditor().getProperties().get(Editor.PROP_PARENT_PATH);
            String instancePath = (String) getVirtualEditor().getProperties().get(Editor.PROP_INSTANCE_PATH);

            // copied from FileActions.SaveServerAction
            // TODO
            AuthentifyUserDialog userDialog = new AuthentifyUserDialog(null, true);
            if (userDialog.showDialog() == AuthentifyUserDialog.CANCEL)
                return null;

            UploadData.Request request = new UploadData.Request();

            request.setData(data);
            request.setPath(dataPath);
            request.setInstancePath(instancePath);
            request.setUuid(uuid);

            request.setUserName(userDialog.getUserName());
            request.setPassword(userDialog.getPassword());
            request.setComment(userDialog.getComment());

            // TODO: remove this. save the state of views.
            getModel().saveComponents(template, request);

            // save the state of the components
            ComponentModel componentModel = new ComponentModel(new DocumentContext(getEditor().getBrowserPane()), getEditor());
            componentModel.saveComponents(request);

            return request;
        }
    }

    public static class XslViewAction extends AbstractDataAction {

        public XslViewAction(VirtualEditor editor) {
            super(editor, java.util.ResourceBundle.getBundle("offset/nodes/client/editor/view/resources/EditorBundle").getString("XSL"));
            setIcon(new javax.swing.ImageIcon(getClass().getResource("/offset/nodes/client/editor/view/resources/sc_xslt.png")));
        }

        protected void setupBrowserPane() {
            getVirtualEditor().getBrowserPane().getDocument().addUndoableEditListener(getVirtualEditor().getUndoableEditListener());
            getVirtualEditor().getHeading().update();
            getVirtualEditor().getStructure().update();
        }

        public void action(ActionEvent e) {
            try {
                getVirtualEditor().getXslPane().getDocument().remove(0, getVirtualEditor().getXslPane().getDocument().getLength());
                StyledXMLWriter writer = new StyledXMLWriter(new StyledDocumentWriter((DefaultStyledDocument) getVirtualEditor().getXslPane().getDocument()));
                writer.parse(getModel().saveTemplate());
                getVirtualEditor().getXslPane().setCaretPosition(0);
                getVirtualEditor().getXslPane().grabFocus();
                ((CardLayout) getVirtualEditor().getMainPanel().getLayout()).show(getVirtualEditor().getMainPanel(), "xsl");
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    public static class DataViewAction extends AbstractDataAction {

        public DataViewAction(VirtualEditor editor) {
            super(editor, java.util.ResourceBundle.getBundle("offset/nodes/client/editor/view/resources/EditorBundle").getString("DataView"));
            setIcon(new javax.swing.ImageIcon(getClass().getResource("/offset/nodes/client/editor/view/resources/sc_data.png")));
        }

        protected void setupBrowserPane() {
            getVirtualEditor().getBrowserPane().getDocument().addUndoableEditListener(getVirtualEditor().getUndoableEditListener());
            getVirtualEditor().getHeading().update();
            getVirtualEditor().getStructure().update();
        }

        public void action(ActionEvent e) {
            try {
                getVirtualEditor().getDataPane().getDocument().remove(0, getVirtualEditor().getDataPane().getDocument().getLength());
                StyledXMLWriter writer = new StyledXMLWriter(new StyledDocumentWriter((DefaultStyledDocument) getVirtualEditor().getDataPane().getDocument()));
                writer.parse(getModel().exportData());
                getVirtualEditor().getDataPane().setCaretPosition(0);
                getVirtualEditor().getDataPane().grabFocus();
                ((CardLayout) getVirtualEditor().getMainPanel().getLayout()).show(getVirtualEditor().getMainPanel(), "data");
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    public static class VirtualPageInstanceViewAction extends AbstractDataAction {

        public VirtualPageInstanceViewAction(VirtualEditor editor) {
            super(editor, java.util.ResourceBundle.getBundle("offset/nodes/client/editor/view/resources/EditorBundle").getString("VirtualPageInstanceView"));
            setIcon(new javax.swing.ImageIcon(getClass().getResource("/offset/nodes/client/editor/view/resources/sc_mapping.png")));
        }

        protected HashMap<String, String> createQueryMap(String[] queries) {
            HashMap<String, String> result = new HashMap<String, String>();

            if (queries != null)
                for (int i = 0; i < queries.length; i++) {
                    if (queries[i] == null || queries[i].length() == 0)
                        continue;
                    String[] query = queries[i].split("=");
                    result.put(query[0], query.length < 2 ? "" : query[1]);
                }

            return result;
        }

        /**
         * Adjust the selection queries.
         *
         * This will include to balance the queries with the sample HTML. The source for the queries
         * is either the server (at start) or the virtual page instance panel (the user may have added query definitions).
         */
        protected void updateSelectionQueries() {
            HashMap<String, String> serverQueries = null;
            if (getVirtualEditor().getVirtualPageInstancePanel().getSelectQueries().length == 0) // first initialization 
                serverQueries = (HashMap<String, String>) getVirtualEditor().getProperties().get(Editor.PROP_SELECT_QUERIES);
            else
                serverQueries = createQueryMap(getVirtualEditor().getVirtualPageInstancePanel().getSelectQueries()); 

            String[] editorQueries = getModel().saveSelectQueries();
            HashSet<String> editorKeys = new HashSet<String>();

            for (String query : editorQueries) {
                if (!serverQueries.containsKey(query))
                    serverQueries.put(query, "");
                editorKeys.add(query);
            }

            if (serverQueries.size() != editorQueries.length) // remove obsolete server queries
                for (String serverKey : serverQueries.keySet()) {
                    if (!editorKeys.contains(serverKey))
                        serverQueries.remove(serverKey);
                }

            getVirtualEditor().getProperties().put(Editor.PROP_SELECT_QUERIES, serverQueries);
            getVirtualEditor().getVirtualPageInstancePanel().updateSelectQueries(serverQueries);
        }

        public void action(ActionEvent e) {
            try {
                Server server = new ServerModel(new URL((String) getVirtualEditor().getProperties().get(Editor.PROP_UPLOAD_TO)));
                String nodeType = getModel().getSchemaName(getModel().findTemplate((Element) ((DefaultMutableTreeNode) getVirtualEditor().getStructureTree().getModel().getRoot()).getUserObject()));
                updateSelectionQueries();

                getVirtualEditor().getVirtualPageInstancePanel().init(getVirtualEditor(), server, nodeType);

                if (e != null)
                    ((CardLayout) getVirtualEditor().getMainPanel().getLayout()).show(getVirtualEditor().getMainPanel(), "mapping");
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    public static class SaveTemplatePageAction extends AbstractDataAction {

        public SaveTemplatePageAction(VirtualEditor editor) {
            super(editor, java.util.ResourceBundle.getBundle("offset/nodes/client/editor/view/resources/EditorBundle").getString("File.saveServer"));
            setIcon(new javax.swing.ImageIcon(getClass().getResource("/offset/nodes/client/editor/view/resources/sc_save2.png")));
        }

        protected String[] createSecondaryQueries(HashMap<String, String> queries) {
            String[] secondaryQueries = new String[queries.size()];
            Iterator<String> it = queries.keySet().iterator();
            int i = 0;
            while (it.hasNext()) {
                String name = it.next();
                String value = queries.get(name);
                secondaryQueries[i++] = name + "=" + value;
            }

            return secondaryQueries;
        }

        public TemplateEditorResult getTemplateEditorResult() throws Exception {
            TemplateEditorResult result = new TemplateEditorResult();

            result.setStyleSheet(getModel().saveTemplate());
            result.setEditorStyleSheet(getModel().saveEditorTemplate());
            result.setSampleHtml(getVirtualEditor().getBrowserPane().getText());
            result.setSelectQueries(getModel().saveSelectQueries());
            result.setElementActions(getModel().getElementActions());

            // secondary queries
            HashMap<String, String> queries = getModel().getSecondaryQueriesFromHtml();
            result.setSecondaryQueries(createSecondaryQueries(queries));

            if (FileActions.getAdditionalStyleSheet() != null)
                result.setCascadingStyleSheet(FileActions.getAdditionalStyleSheet());

            return result;
        }

        public void action(ActionEvent e) throws Exception {
            TemplateEditorResult result = getTemplateEditorResult();

            getVirtualEditor().getResult().setValue(result);
            getVirtualEditor().getResult().setState(ResultState.Success);

            getVirtualEditor().close();
        }
    }

    public static class SaveVirtualPageAction extends SaveTemplatePageAction {

        public SaveVirtualPageAction(VirtualEditor editor) {
            super(editor);
        }

        public void exit() throws MalformedURLException {
            URL url = new URL(getVirtualEditor().getProperties().get(((String) Editor.PROP_DOCUMENT_URL)) + (String) getVirtualEditor().getProperties().get(Editor.PROP_INSTANCE_PATH));
            getVirtualEditor().getAppletContext().showDocument(url);

            getVirtualEditor().close();
        }

        /**
         * Initialize the virtual page instance panel, if required.
         */
        protected void initVirtualPageInstancePanel() {
            if (getVirtualEditor().getVirtualPageInstancePanel().isInitialized())
                return;
            
            VirtualPageInstanceViewAction virtualPageInstanceViewAction = new VirtualPageInstanceViewAction(getVirtualEditor());
            virtualPageInstanceViewAction.action(null);
        }

        public void action(ActionEvent e) throws Exception {
            TemplateEditorResult template = getTemplateEditorResult();
            String before = (String) getVirtualEditor().getProperties().get(VirtualPageEditorApplet.PROP_SAMPLE_HTML);
            if (template.getSampleHtml().equals(before)) // do not store template, if nothing has changed
                template = null;

            initVirtualPageInstancePanel();
            TemplateInstanceEditorResult instance = new TemplateInstanceEditorResult();
            instance.setSecondaryQueries(getVirtualEditor().getVirtualPageInstancePanel().getSecondaryQueries());
            instance.setSelectQueries(getVirtualEditor().getVirtualPageInstancePanel().getSelectQueries());

            Server server = new ServerModel(new URL((String) getVirtualEditor().getProperties().get(Editor.PROP_UPLOAD_TO)));

            UploadVirtualPage.Request request = new UploadVirtualPage.Request();
            request.setTemplateInfo(template);
            request.setInstanceInfo(instance);
            request.setInstancePath((String) getVirtualEditor().getProperties().get(Editor.PROP_INSTANCE_PATH));

            UploadVirtualPage.Response response = (UploadVirtualPage.Response) server.sendRequest(request);

            exit();
        }
    }

    public static class ExitVirtualPageAction extends SaveVirtualPageAction {

        public ExitVirtualPageAction(VirtualEditor editor) {
            super(editor);
            String name = java.util.ResourceBundle.getBundle("offset/nodes/client/editor/view/resources/EditorBundle").getString("File.exit");
            putValue(NAME, name);
            putValue(SHORT_DESCRIPTION, name);
            setIcon(new javax.swing.ImageIcon(getClass().getResource("/offset/nodes/client/editor/view/resources/sc_exit2.png")));            
        }

        public void action(ActionEvent e) throws Exception {
            exit();
        }
    }

    public static class ExitTemplatePageAction extends AbstractDataAction {

        public ExitTemplatePageAction(VirtualEditor editor) {
            super(editor, java.util.ResourceBundle.getBundle("offset/nodes/client/editor/view/resources/EditorBundle").getString("File.exit"));
            setIcon(new javax.swing.ImageIcon(getClass().getResource("/offset/nodes/client/editor/view/resources/sc_stop.png")));

        }

        public void action(ActionEvent e) throws Exception {
            getVirtualEditor().getResult().setState(ResultState.Failure);
            getVirtualEditor().close();
        }
    }

    public static class SaveTemplateAsAction extends AbstractDataAction {

        public SaveTemplateAsAction(VirtualEditor editor) {
            super(editor, localizer.getString("SaveTemplateAs"));
        }

        public void action(ActionEvent e) throws Exception {
            if (getVirtualEditor().getChooser().showOpenDialog(getVirtualEditor().getBrowserPane()) != javax.swing.JFileChooser.APPROVE_OPTION)
                return;

            OutputStream out = new FileOutputStream(getVirtualEditor().getChooser().getSelectedFile().getPath());
            Element element = (Element) ((DefaultMutableTreeNode) getVirtualEditor().getStructureTree().getSelectionPath().getLastPathComponent()).getUserObject();
            getModel().addTemplate(element, getVirtualEditor().getChooser().getSelectedFile());
            getModel().saveTemplate(element, out);
            out.close();
        }
    }

    public static class NewTemplateAction extends AbstractDataAction {

        public NewTemplateAction(VirtualEditor editor) {
            super(editor, localizer.getString("NewTemplate"));
        }

        protected void removeObsoletePopups(JPopupMenu popup) {
            for (int i = 0; i < popup.getComponentCount(); i++) {
                Component c = popup.getComponent(i);
                if (c == mapData)
                    popup.remove(c);
            }
        }

        public void action(ActionEvent e) throws Exception {
            TreePath[] paths = getVirtualEditor().getStructureTree().getSelectionPaths();
            if (paths.length <= 0)
                return;

            Element start = (Element) ((DefaultMutableTreeNode) paths[0].getLastPathComponent()).getUserObject();
            Element end = (Element) ((DefaultMutableTreeNode) paths[paths.length - 1].getLastPathComponent()).getUserObject();

            if (getVirtualEditor().getChooser().showOpenDialog(getVirtualEditor().getBrowserPane()) != javax.swing.JFileChooser.APPROVE_OPTION)
                return;

            getVirtualEditor().getStructure().ignoreEvents(true);
            Element templ = getModel().insertDataBlock(start, end, schema, "/" + schemaRoot, getVirtualEditor().getChooser().getSelectedFile());
            getVirtualEditor().getStructure().ignoreEvents(false);

            ((DefaultTreeModel) getVirtualEditor().getStructureTree().getModel()).reload((TreeNode) paths[0].getLastPathComponent());

            getVirtualEditor().getStructureScroll().setViewportView(getVirtualEditor().getStructureTree());
            ((CardLayout) getVirtualEditor().getTreePanel().getLayout()).show(getVirtualEditor().getTreePanel(), "structure");

            removeObsoletePopups(getVirtualEditor().getStructure().getPopup());
            JPopupMenu caretPopup = ((EditorCaret) getVirtualEditor().getBrowserPane().getCaret()).getPopupMenu();
            removeObsoletePopups(caretPopup);
            schemaRoot = null;

            getVirtualEditor().getStructure().addNodePopups(getDataPopups(getVirtualEditor()));
        }
    }

    public static class AbstractMapElementAction extends AbstractDataAction {

        int schemaType;

        public AbstractMapElementAction(VirtualEditor editor, String name, int schemaType) {
            super(editor, name);
            this.schemaType = schemaType;
        }

        public void action(ActionEvent e) throws Exception {
            DefaultMutableTreeNode structureNode = (DefaultMutableTreeNode) getVirtualEditor().getStructureTree().getSelectionPath().getLastPathComponent();
            Element element = (Element) structureNode.getUserObject();

            // select schema, i.e. node type
            SelectSchemaDialog dialog = new SelectSchemaDialog(getVirtualEditor().getStructureTree(),
                    (NodeTypes) getVirtualEditor().getProperties().get(Editor.PROP_NODE_TYPES), schemaType);
            if (element.getAttributes().isDefined(DataModel.ATTRIBUTE_NAME_DATA))
                dialog.setSchemaName((String) element.getAttributes().getAttribute(DataModel.ATTRIBUTE_NAME_DATA));
            dialog.show();
            if (dialog.getReturnStatus() == SelectSchemaDialog.RET_OK) {
                HashMap<String, String> queries = (HashMap<String, String>) getVirtualEditor().getProperties().get(Editor.PROP_SECONDARY_QUERIES);

                String data = dialog.getSchemaName();
                if (dialog.getSchemaType() == SelectSchemaDialog.SCHEMA_NEW) { // secondary Query
                    String key = getModel().getSecondaryQueryKey(element);
                    if (key == null) {
                        int index = getModel().getMaximumSecondaryQueryIndex();
                        if (index < queries.size()) { // some secondary query must have been removed in the editor
                            int size = queries.size();
                            for (int i = index + 1; i <= size; i++) {
                                queries.remove(DataModel.SECONDARY_QUERY.substring(1) + i);
                            }
                        }
                        key = DataModel.SECONDARY_QUERY + (index + 1);
                    }
                    queries.put(key.substring(1), dialog.getSchemaName());

                    getModel().mapSchemaElement(element, data);

                    data = key + "/" + data;
                } else if (getModel().isSecondaryQuery(element)) {
                    String value = getModel().getSecondaryQueryValue(element);
                    if (value != null && value.indexOf("/") < 0) { // was a secondary query root
                        getModel().removeSecondaryQueries(element);
                        queries.remove(getModel().getSecondaryQueryKey(element));
                    }
                }

                getModel().mapDataElement(element, data);
                if (dialog.getSchemaObject() instanceof SchemaProperty 
                        && ((SchemaProperty) dialog.getSchemaObject()).getProperty() != null
                        && ((SchemaProperty) dialog.getSchemaObject()).getProperty().getRequiredType() == PropertyType.BINARY)
                    getModel().markBinary(element);

                ((DefaultTreeModel) getVirtualEditor().getStructureTree().getModel()).reload(structureNode);
                getVirtualEditor().getStructure().refresh();
            }
        }
    }

    public static class MapElementToNewTypeAction extends AbstractMapElementAction {

        public MapElementToNewTypeAction(VirtualEditor editor) {
            super(editor, localizer.getString("MapPresentation"), SelectSchemaDialog.SCHEMA_NEW);
        }
    }

    public static class MapElementToExtendedTypeAction extends AbstractMapElementAction {

        public MapElementToExtendedTypeAction(VirtualEditor editor) {
            super(editor, localizer.getString("MapPresentation"), SelectSchemaDialog.SCHEMA_EXTENDED);
        }
    }

    /**
     * Let the selected element refer to an external binary page. In the virtual page,
     * the external page content will be substitute the element content.
     */
    public static class MapElementToPageAction extends AbstractDataAction {

        public MapElementToPageAction(VirtualEditor editor) {
            super(editor, localizer.getString("MapPresentation"));
        }

        public void action(ActionEvent evt) throws Exception {
            DefaultMutableTreeNode dataNode = (DefaultMutableTreeNode) getVirtualEditor().getStructureTree().getSelectionPath().getLastPathComponent();
            Element e = (Element) dataNode.getUserObject();
            getModel().markAsPageReference(e, true);
        }
    }

    /**
     * Let the selected element refer to an external page. In the virtual page,
     * the external page content will be substitute the element content.
     */
    public static class MapElementToComponentAction extends AbstractDataAction {

        public MapElementToComponentAction(VirtualEditor editor) {
            super(editor, localizer.getString("MapPresentation"));
        }

        public void action(ActionEvent evt) throws Exception {
            DefaultMutableTreeNode dataNode = (DefaultMutableTreeNode) getVirtualEditor().getStructureTree().getSelectionPath().getLastPathComponent();
            Element e = (Element) dataNode.getUserObject();
            getModel().markAsComponentReference(e, true);
        }
    }

    /**
     * Let the selected element refer to an external binary page. In the virtual page,
     * the external page content will be substitute the element content.
     */
    public static class RemovePageMappingAction extends AbstractDataAction {

        public RemovePageMappingAction(VirtualEditor editor) {
            super(editor, localizer.getString("MapPresentation"));
        }

        public void action(ActionEvent evt) throws Exception {
            DefaultMutableTreeNode dataNode = (DefaultMutableTreeNode) getVirtualEditor().getStructureTree().getSelectionPath().getLastPathComponent();
            Element e = (Element) dataNode.getUserObject();
            getModel().markAsPageReference(e, false);
        }
    }

   /**
     * Let the selected element refer to an external component page. In the virtual page,
     * the external page content will be substitute the element content.
     */
    public static class RemoveComponentMappingAction extends AbstractDataAction {

        public RemoveComponentMappingAction(VirtualEditor editor) {
            super(editor, localizer.getString("MapPresentation"));
        }

        public void action(ActionEvent evt) throws Exception {
            DefaultMutableTreeNode dataNode = (DefaultMutableTreeNode) getVirtualEditor().getStructureTree().getSelectionPath().getLastPathComponent();
            Element e = (Element) dataNode.getUserObject();
            getModel().markAsComponentReference(e, false);
        }
    }

    public static class MapStringAction extends AbstractDataAction {

        public MapStringAction(VirtualEditor editor) {
            super(editor, localizer.getString("MapPresentation"));
        }

        public void action(ActionEvent e) throws Exception {
            //DefaultMutableTreeNode structureNode = (DefaultMutableTreeNode) getVirtualEditor().getStructureTree().getSelectionPath().getLastPathComponent();
            DefaultMutableTreeNode schemaNode = (DefaultMutableTreeNode) getVirtualEditor().getSchemaTree().getSelectionPath().getLastPathComponent();
            Stack path = new Stack();
            while (schemaNode != null) {
                path.push(schemaNode.getUserObject());
                schemaNode = (DefaultMutableTreeNode) schemaNode.getParent();
            }
            StringBuffer name = new StringBuffer();
            while (path.size() > 0) {
                name.append("/" + path.pop());
            }

            int start = getVirtualEditor().getBrowserPane().getSelectionStart();
            int end = getVirtualEditor().getBrowserPane().getSelectionEnd();
            getModel().mapDataElement(start, end, name.toString());
            //((DefaultTreeModel)getVirtualEditor().getStructureTree().getModel()).reload(structureNode);
        }
    }

    /**
     * Mark the selected element as read-only.
     */
    public static class MarkReadOnlyAction extends AbstractDataAction {

        public MarkReadOnlyAction(VirtualEditor editor) {
            super(editor, localizer.getString("data.MarkReadOnly"));
        }

        public void action(ActionEvent evt) throws Exception {
            DefaultMutableTreeNode dataNode = (DefaultMutableTreeNode) getVirtualEditor().getStructureTree().getSelectionPath().getLastPathComponent();
            Element e = (Element) dataNode.getUserObject();
            getModel().markReadOnly(e);

            ((DefaultTreeModel) getVirtualEditor().getStructureTree().getModel()).reload(dataNode);
            getVirtualEditor().getStructure().refresh();
        }
    }

    /**
     * Select an existing template for the element.
     */
    public static class UseTemplateAction extends AbstractDataAction {

        public UseTemplateAction(VirtualEditor editor) {
            super(editor, localizer.getString("data.useTemplate"));
        }

        public void action(ActionEvent evt) throws Exception {
            DefaultMutableTreeNode dataNode = (DefaultMutableTreeNode) getVirtualEditor().getStructureTree().getSelectionPath().getLastPathComponent();
            Element e = (Element) dataNode.getUserObject();
            String nodeType = getModel().getData(e);
            if (nodeType.startsWith(DataModel.SECONDARY_QUERY))
                nodeType = nodeType.substring(nodeType.indexOf(Constants.PATH_SEPARATOR));
            String templateUuid = getModel().getExistingTemplateUuid(e);


            GetTemplatesForNodeTypePath.Request request = new GetTemplatesForNodeTypePath.Request();
            request.setNodeTypePath(nodeType);
            request.setInstancePath((String) getVirtualEditor().getProperties().get(Editor.PROP_INSTANCE_PATH));
            try {
                String dialogUrl = (String) getVirtualEditor().getProperties().get(Editor.PROP_UPLOAD_TO);
                if (dialogUrl == null)
                    return;

                ServerModel server = new ServerModel(new URL(dialogUrl));
                GetTemplatesForNodeTypePath.Response response = (GetTemplatesForNodeTypePath.Response) server.sendRequest(request);

                // display the enumerated value panel and let the user select a value
                ExistingTemplates panel = new ExistingTemplates(response.getTemplates(), templateUuid);
                SimpleDialog dialog = new SimpleDialog(null, true, panel);
                panel.setContainer(dialog);
                dialog.setSize(350, 250);
                dialog.setLocation(100, 100);
                dialog.setVisible(true);
                if (dialog.getReturnStatus() == SimpleDialog.OK) {
                    GetTemplatesForNodeTypePath.TemplateInformation selected = panel.getResult();
                    getModel().setExistingTemplate(e, selected.getNodeType(), selected.getUuid(), selected.getSampleHtml());
                }
            } catch (Exception ex) {
                Logger.getLogger(EnumeratedValueChooser.class.getName()).log(Level.SEVERE, null, ex);
            }

            ((DefaultTreeModel) getVirtualEditor().getStructureTree().getModel()).reload(dataNode);
            getVirtualEditor().getStructure().refresh();
        }
    }
    
   /**
     * Add element action to the selected element
     */
    public static class MapElementActionAction extends AbstractDataAction {

        public MapElementActionAction(VirtualEditor editor) {
            super(editor, getBundle().getString("data.ElementAction"));
        }

        public void action(ActionEvent evt) throws Exception {
            DefaultMutableTreeNode dataNode = (DefaultMutableTreeNode) getVirtualEditor().getStructureTree().getSelectionPath().getLastPathComponent();
            Element e = (Element) dataNode.getUserObject();

            // display the enumerated value panel and let the user select a value
            SelectElementActionNameChooserPanel panel = new SelectElementActionNameChooserPanel();
            SimpleDialog dialog = new SimpleDialog(null, true, panel);
            dialog.setSize(450, 160);
            dialog.setLocation(100, 100);
            dialog.setVisible(true);
            if (dialog.getReturnStatus() == SimpleDialog.OK)
                getModel().getBrowserElementActionModel().add(e, panel.getResult());

            ((DefaultTreeModel) getVirtualEditor().getStructureTree().getModel()).reload(dataNode);
            getVirtualEditor().getStructure().refresh();
        }
    }
    
   /**
     * Mark the selected element as read-write
     */
    public static class RemoveElementActionAction extends AbstractDataAction {

        public RemoveElementActionAction(VirtualEditor editor) {
            super(editor, getBundle().getString("data.RemoveElementAction"));
        }

        public void action(ActionEvent evt) throws Exception {
            DefaultMutableTreeNode dataNode = (DefaultMutableTreeNode) getVirtualEditor().getStructureTree().getSelectionPath().getLastPathComponent();
            Element e = (Element) dataNode.getUserObject();

            getModel().getBrowserElementActionModel().add(e, null);

            ((DefaultTreeModel) getVirtualEditor().getStructureTree().getModel()).reload(dataNode);
            getVirtualEditor().getStructure().refresh();
        }
    }

    /**
     * Mark the selected element as read-write
     */
    public static class MapQueryAction extends AbstractDataAction {

        public MapQueryAction(VirtualEditor editor) {
            super(editor, localizer.getString("data.MapQuery"));
        }

        public void action(ActionEvent evt) throws Exception {
            DefaultMutableTreeNode dataNode = (DefaultMutableTreeNode) getVirtualEditor().getStructureTree().getSelectionPath().getLastPathComponent();
            Element e = (Element) dataNode.getUserObject();

            // display the enumerated value panel and let the user select a value
            SelectQueryNameChooserPanel panel = new SelectQueryNameChooserPanel();
            SimpleDialog dialog = new SimpleDialog(null, true, panel);
            dialog.setSize(450, 160);
            dialog.setLocation(100, 100);
            dialog.setVisible(true);
            if (dialog.getReturnStatus() == SimpleDialog.OK)
                getModel().setQueryName(e, panel.getResult());

            ((DefaultTreeModel) getVirtualEditor().getStructureTree().getModel()).reload(dataNode);
            getVirtualEditor().getStructure().refresh();
        }
    }

 
    /**
     * Mark the selected element as read-write
     */
    public static class RemoveQueryAction extends AbstractDataAction {

        public RemoveQueryAction(VirtualEditor editor) {
            super(editor, localizer.getString("data.RemoveQuery"));
        }

        public void action(ActionEvent evt) throws Exception {
            DefaultMutableTreeNode dataNode = (DefaultMutableTreeNode) getVirtualEditor().getStructureTree().getSelectionPath().getLastPathComponent();
            Element e = (Element) dataNode.getUserObject();

            getModel().setQueryName(e, null);

            ((DefaultTreeModel) getVirtualEditor().getStructureTree().getModel()).reload(dataNode);
            getVirtualEditor().getStructure().refresh();
        }
    }

    /**
     * Mark the selected element as read-write
     */
    public static class MarkReadWriteAction extends AbstractDataAction {

        public MarkReadWriteAction(VirtualEditor editor) {
            super(editor, localizer.getString("data.MarkReadWrite"));
        }

        public void action(ActionEvent evt) throws Exception {
            DefaultMutableTreeNode dataNode = (DefaultMutableTreeNode) getVirtualEditor().getStructureTree().getSelectionPath().getLastPathComponent();
            Element e = (Element) dataNode.getUserObject();
            getModel().markReadWrite(e);

            ((DefaultTreeModel) getVirtualEditor().getStructureTree().getModel()).reload(dataNode);
            getVirtualEditor().getStructure().refresh();
        }
    }

    /**
     * Delete a row in a data table
     */
    public static class DeleteDataRowAction extends AbstractDataAction {

        DeleteRowAction deleteRowAction;
        SetCaretAction setCaretAction;

        public DeleteDataRowAction(VirtualEditor editor) {
            super(editor, java.util.ResourceBundle.getBundle("offset/nodes/client/editor/view/resources/EditorBundle").getString("Delete_Row"));
            setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_R, java.awt.event.InputEvent.SHIFT_MASK | java.awt.event.InputEvent.CTRL_MASK));
            setIcon(new javax.swing.ImageIcon(getClass().getResource("/offset/nodes/client/editor/view/resources/sc_deleterows.png")));

            deleteRowAction = new DeleteRowAction(editor);
            setCaretAction = new SetCaretAction(editor);
        }

        public void action(ActionEvent evt) throws Exception {
            deleteRowAction.action(evt);
            SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {
                    try {
                        setCaretAction.action(null);
                    } catch (Exception ex) {
                        Logger.getLogger(DataActions.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            });
        }
    }

    public static class TabAction extends CommonTabAction {

        public TabAction(VirtualEditor editor) {
            super(editor, localizer.getString("data.tab"));
            setIcon(new javax.swing.ImageIcon(getClass().getResource("/offset/nodes/client/editor/view/resources/sc_tab.png")));
            forward = true;
        }
        
        public void action(ActionEvent e) throws Exception {
            int pos = getVirtualEditor().getBrowserPane().getCaretPosition();
            Element element = getModel().nextDataElement(pos);
            selectElement(element);
        }
    }

    public static class BackTabAction extends CommonTabAction {

        public BackTabAction(VirtualEditor editor) {
            super(editor, localizer.getString("data.backtab"));
            setIcon(new javax.swing.ImageIcon(getClass().getResource("/offset/nodes/client/editor/view/resources/sc_backtab.png")));
            forward = false;
        }

        public void action(ActionEvent e) throws Exception {
            int pos = getVirtualEditor().getBrowserPane().getCaretPosition();
            Element element = getModel().previousDataElement(pos);
            selectElement(element);        
        }
    }

    protected static abstract class CommonTabAction extends AbstractDataAction {
        ComponentModel componentModel;
        boolean forward = true;

        public CommonTabAction(VirtualEditor editor, String name) {
            super(editor, name);
            componentModel = getComponentModel();
        }
        
        public void selectElement(Element element) {
            super.selectElement(element);
            if (componentModel.isComponent(element))
                componentModel.grabFocus(element);
        }
        
        public void grabFocus(JEditorPane pane) {
        }

    }

    public static class CommonCaretAction extends SetCaretAction {

        public CommonCaretAction(VirtualEditor editor, String name) {
            super(editor, name);
        }

        @Override
        public void action(ActionEvent e) throws Exception {
            adjustDataFocus();
        }
    }

    static class UpdateActions {

        VirtualEditor virtualEditor;
        DataModel model;

        public UpdateActions(VirtualEditor editor) {
            this.virtualEditor = editor;
            this.model = new DataModel(new DocumentContext(getVirtualEditor().getBrowserPane()), getVirtualEditor().getProperties());
        }

        public VirtualEditor getVirtualEditor() {
            return virtualEditor;
        }

        public void update() {
            int pos = getVirtualEditor().getBrowserPane().getCaretPosition();
            if (model.isFromThisTable(pos))
                getVirtualEditor().getDeleteRowButton().getAction().setEnabled(true);
            else
                getVirtualEditor().getDeleteRowButton().getAction().setEnabled(false);
        }
    }

    public static class SetCaretAction extends AbstractDataAction implements UserCaretListener {

        public SetCaretAction(VirtualEditor editor) {
            this(editor, "data-set-caret");
        }

        public SetCaretAction(VirtualEditor editor, String name) {
            super(editor, name);
        }

        public void userSetCaret() {
            adjustDataFocus();
        }

        @Override
        public void action(ActionEvent e) throws Exception {
            adjustDataFocus();
        }

        public void adjustDataFocus() {
            DataNavigationFilter navigationFilter = getNavigationFilter();
            if (navigationFilter == null)
                return;
            adjustDataFocus(navigationFilter.getOldPos());
        }

        public void adjustDataFocus(int oldPos) {
            DataModel model = new DataModel(new DocumentContext(getVirtualEditor().getBrowserPane()), getVirtualEditor().getProperties());
            DataNavigationFilter navigationFilter = getNavigationFilter();
            if (navigationFilter == null)
                return;

            try {
                navigationFilter.setFilter(false);
                int pos = navigationFilter.getNewPos();
                Element e = model.moveCaret(oldPos, navigationFilter.getNewPos());
                getVirtualEditor().getStructure().selectElement(e);
                navigationFilter.setFilter(true);
                setPosition(pos);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static class ShowViewAction extends AbstractDataAction {

        public ShowViewAction(VirtualEditor editor) {
            this(editor, "data-show-view");
        }

        public ShowViewAction(VirtualEditor editor, String name) {
            super(editor, name);
        }

        @Override
        public void action(ActionEvent evt) throws Exception {
            DataModel model = new DataModel(new DocumentContext(getVirtualEditor().getBrowserPane()), getVirtualEditor().getProperties());

            try {
                int pos = getVirtualEditor().getBrowserPane().getCaretPosition();
                Element e = model.showView(pos);
                if (e != null) {
                    getNavigationFilter().setFilter(false);
                    selectElement(e);
                    getNavigationFilter().setFilter(true);
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    public static class OpenSchemaAction extends AbstractDataAction implements TreeSelectionListener {

        public OpenSchemaAction(VirtualEditor editor) {
            super(editor, localizer.getString("Open_Schema_..."));
            setIcon(new javax.swing.ImageIcon(getClass().getResource("/offset/nodes/client/editor/view/resources/sc_xml.png")));
        }

        protected void extendCaretPopup(JPopupMenu popup, String elementName) {
            if (mapData == null)
                mapData = new JMenuItem();
            mapData.setAction(new DataActions.MapStringAction(getVirtualEditor()));
            mapData.setText(java.text.MessageFormat.format(java.util.ResourceBundle.getBundle("offset/nodes/client/editor/view/resources/EditorBundle").getString("MapData"), new Object[]{elementName}));
            if (popup.getComponentIndex(mapData) < 0)
                popup.add(mapData);
        }

        public void valueChanged(TreeSelectionEvent e) {
            if (((JTree) e.getSource()).getSelectionCount() > 0) {
                JPopupMenu caretPopup = ((EditorCaret) getVirtualEditor().getBrowserPane().getCaret()).getPopupMenu();
                extendCaretPopup(caretPopup, e.getPath().getLastPathComponent().toString());

                getVirtualEditor().getStructure().refresh();
            }
        }

        public void createSchemaTree(String schemaPath) {
            DataModel model = getModel();
            try {
                createSchemaTree(model.getInputStream(schemaPath));
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

        protected void createSchemaTree(InputStream in) throws Exception {
            SchemaTree schemaTree = new SchemaTree(in, getVirtualEditor().getSchemaTree());
            schemaRoot = ((SchemaTree.SchemaElement) schemaTree.getRoot().getUserObject()).getName();

            getVirtualEditor().getStructure().refresh();

            getVirtualEditor().getSchemaTree().addTreeSelectionListener(this);
            getVirtualEditor().getTemplateStructureScroll().setViewportView(getVirtualEditor().getStructureTree());

            ((CardLayout) getVirtualEditor().getTreePanel().getLayout()).show(getVirtualEditor().getTreePanel(), "template");

        }

        public void action(ActionEvent e) {
            if (getVirtualEditor().getChooser().showOpenDialog(getVirtualEditor().getBrowserPane()) != javax.swing.JFileChooser.APPROVE_OPTION)
                return;

            try {
                schema = getVirtualEditor().getChooser().getSelectedFile();
                InputStream in = new FileInputStream(schema);
                createSchemaTree(in);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
}
